/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2019年1月18日
 * V4.0
 */
package com.jphenix.kernel.classloader;
//#region 【引用区】
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;

import com.jphenix.kernel.baseobject.instanceb.ABase;
import com.jphenix.share.lang.SString;
import com.jphenix.share.util.BaseUtil;
import com.jphenix.share.util.SFilesUtil;
import com.jphenix.standard.docs.BeanInfo;
import com.jphenix.standard.docs.ClassInfo;
import com.jphenix.standard.exceptions.MsgException;
//#endregion

//#region  【说明区】
/**
 * 全局包资源管理服务
 * 
 * 2019-11-04 修改了，扩展类路径中引用了其它扩展类路径时无效的错误
 * 2020-05-12 避免重复加载资源
 * 2020-05-24 增加避免重复加载机制纯属画蛇添足，导致无法加载扩展库
 *            之前在全局变量中增加了一个list，用来保存所有已经加载过的文件路径。但是遇到另外一个问题
 *            类A加载了扩展库，在当前类中的pUrlMap私有包路径中增加了对照关系。但是类B也加载了同样的
 *            扩展库，也需要增加对照关系，结果被避免重复的list拦截，认为已经加载过，然后就放弃加载了
 *            导致类B无法加载扩展库
 * 2022-02-08 优化了一段语句
 * 2024-05-21 取消了无用并且复杂不好用的ext_lib扩展库功能，改用 WEB-INF/lib 中创建子文件夹，将第三方包分类保存
 * 2024-05-21 增加了通过同一个路径获取多个资源的方法
 * 2024-08-17 适配了嵌入Spring架构
 *            
 * @author MBG
 * 2019年1月18日
 */
//#endregion
@ClassInfo({"2024-08-17 23:20","全局包资源管理服务"})
@BeanInfo({"resourceservice"})
public class ResourceService extends ABase {

	//#region 【声明区】
	//全局包路径序列
	private final List<String>                        urlList    = new ArrayList<>();
	// 资源容器 key：压缩包中的相对路径  value：资源对象序列（因为多个压缩包中会有相同相对路径的资源）
	private final HashMap<String,List<ResourceEntry>> sourceMap = new HashMap<>();
	//#endregion
	
	//#region getSources(sourceKey) 获取指定资源
	/**
	 * 获取指定资源
	 * @param sourceKey 资源主键
	 * @return          资源信息对象序列
	 * 2019年1月18日
	 * @author MBG
	 */
	public synchronized List<ResourceEntry> getSources(String sourceKey) {
		if(sourceKey==null || sourceKey.length()<1) {
			return null;
		}
		if(sourceKey.startsWith("/")) {
			sourceKey = sourceKey.substring(1);
		}
		// 获取指定路径的资源信息序列
		List<ResourceEntry> res = sourceMap.get(sourceKey);
		if(res==null) {
			return null;
		}
		return res;
	}
	//#endregion
	
	//#region getSource(sourceKey) 获取指定资源
	/**
	 * 获取指定资源
	 * @param sourceKey 资源主键
	 * @return          资源信息对象
	 * 2019年1月18日
	 * @author MBG
	 */
	public synchronized ResourceEntry getSource(String sourceKey) {
		// 获取指定路径的资源信息序列
		List<ResourceEntry> res = getSources(sourceKey);
		if(res!=null && res.size()>0) {
			// 获取最后一个（后一个会覆盖前一个）
			return res.get(res.size()-1);
		}
		return null;
	}
	//#endregion
	
	//#region addSource(entry,zipFile,basePath) 添加一个资源元素
	/**
	 * 添加一个资源元素
	 * @param entry     压缩包元素项
	 * @param zipFile   压缩包对象
	 * @param basepath  压缩包内根路径
	 * 2024年8月10日
	 * @author MBG
	 */
	public void addSource(ZipEntry entry,ZipFile zipFile,String basePath) {
		if(entry==null) {
			return;
		}
		// 压缩包内路径
		String entryPath = entry.getName();
		if(entryPath.toLowerCase().endsWith(".jar")) {
			addZipSource(entry,zipFile);
			return;
		}
		// 构建资源元素
		ResourceEntry re = new ResourceEntry();
		re.codeBase      = zipFile.getName();
		re.inJar         = true;
		re.lastModified  = entry.getTime();
		re.source        = entryPath;
		
		// 字节输出流
		ByteArrayOutputStream bos    = new ByteArrayOutputStream();
		InputStream           is     = null;
		byte[]                buffer = new byte[2048];
		int                   readSize;
		try {
			is = zipFile.getInputStream(entry);
			while((readSize=is.read(buffer))!=-1) {
				bos.write(buffer,0,readSize);
			}
			re.fileBytes = bos.toByteArray();
		}catch(Exception e) {
			e.printStackTrace();
		}finally {
			try {
				is.close();
			}catch(Exception e2) {}
			try {
				bos.close();
			}catch(Exception e2) {}
		}
		
		//保留开头的/ 所有资源第一个字符都是/
		re.sourceSubPath = re.source.substring(basePath.length());
		//资源主键
		String key = re.sourceSubPath;
		if(key!=null && key.startsWith("/")) {
			key = key.substring(1);
		}
		synchronized(sourceMap) {
			//放入资源文件中
			List<ResourceEntry> entrys = sourceMap.get(key);
			if(entrys==null) {
				entrys = new ArrayList<>();
				sourceMap.put(key,entrys);
			}
			entrys.add(re);
		}
	}
	//#endregion
	
	//#region addZipSource(entry,zipFile) 从压缩包中加载内部压缩包中的资源
	/**
	 * 从压缩包中加载内部压缩包中的资源
	 * @param entry    压缩包文件项
	 * @param zipFile  压缩包对象
	 * 2024年8月10日
	 * @author MBG
	 */
	private void addZipSource(ZipEntry entry,ZipFile zipFile) {
		// 字节输出流
		ByteArrayOutputStream bos    = new ByteArrayOutputStream();
		InputStream           is     = null;
		ZipInputStream        zipIs  = null;
		byte[]                buffer = new byte[2048];
		int                   readSize;
		try {
			is = zipFile.getInputStream(entry);
			while((readSize=is.read(buffer))!=-1) {
				bos.write(buffer,0,readSize);
			}
			// 构建压缩包流
			zipIs = new ZipInputStream(new ByteArrayInputStream(bos.toByteArray()));
			
			ZipEntry ele = zipIs.getNextEntry();
			
			ResourceEntry       re;     // 资源元素
			List<ResourceEntry> entrys; // 已设置的资源序列
			while(ele!=null) {
				if(ele.isDirectory()) {
					zipIs.closeEntry();
					ele = zipIs.getNextEntry();
					continue;
				}
				bos = new ByteArrayOutputStream();
				while((readSize=zipIs.read(buffer))!=-1) {
					bos.write(buffer,0,readSize);
				}
				// 构建资源元素
				re               = new ResourceEntry();
				// 注意，这里的路径是多个jar嵌套路径，只有末级的压缩包内路径开头不带/
				re.codeBase      = zipFile.getName()+"!/"+entry.getName();
				re.inJar         = true;
				re.lastModified  = ele.getTime();
				re.source        = ele.getName();
				
				re.fileBytes     = bos.toByteArray();
				
				//保留开头的/ 所有资源第一个字符都是/
				re.sourceSubPath = re.source;
				//资源主键
				String key = re.sourceSubPath;
				if(key!=null && key.startsWith("/")) {
					key = key.substring(1);
				}
				synchronized(sourceMap) {
					//放入资源文件中
					entrys = sourceMap.get(key);
					if(entrys==null) {
						entrys = new ArrayList<>();
						sourceMap.put(key,entrys);
					}
					entrys.add(re);
				}
				
				zipIs.closeEntry();
				ele = zipIs.getNextEntry();
			}
			
		}catch(Exception e) {
			e.printStackTrace();
		}finally {
			try {
				is.close();
			}catch(Exception e2) {}
			try {
				bos.close();
			}catch(Exception e2) {}
		}
	}
	//#endregion
	
	//#region removeSource(sourceKey) 移除资源
	/**
	 * 移除资源
	 * 注意：这里无需传入扩展包，因为没有什么场景需要从扩展包中移除资源
	 * 以后可能会增加使用超时后，自动移除扩展包资源，目前暂时不需要。在实际需要的时候再考虑这个功能
	 * @param sourceKey 资源主键
	 * 2019年1月18日
	 * @author MBG
	 */
	public void removeSource(String sourceKey) {
		if(sourceKey==null) {
			return;
		}
		if(sourceKey.startsWith("/")) {
			sourceMap.remove(sourceKey.substring(1));
		}else {
			sourceMap.remove(sourceKey);
		}
	}
	//#endregion
	
	//#region getURLs() 返回全局包路径数组
	/**
	 * 返回全局包路径数组
	 * @return 全局包路径数组
	 * 2019年1月18日
	 * @author MBG
	 */
	public URL[] getURLs() {
		//构建返回值
		URL[] reUrl = new URL[urlList.size()];
		String path; //路径元素
		for(int i=0;i<urlList.size();i++) {
			path = urlList.get(i);
			try {
				reUrl[i] = new URL("file:/"+path);
			}catch(Exception e) {
				e.printStackTrace();
			}
		}
		return reUrl;
	}
	//#endregion
	
	//#region addZipSource(zipFilePath) 增加压缩包中的资源信息
	/**
	 * 增加压缩包中的资源信息
	 * 刘虻
	 * 2010-8-31 下午01:11:25
	 */
	protected void addZipSource(String zipFilePath) {
		//搜索结果路径序列
		ArrayList<String> pathList = new ArrayList<String>();
		//搜索压缩包
		SFilesUtil.searchZipFilePath(pathList,zipFilePath,null,null,false);
		if(pathList.size()>0) {
			if(urlList.contains(zipFilePath)) {
				return;
			}
			urlList.add(zipFilePath);
			//构建压缩文件对象
			File zipFile = new File(zipFilePath);
			for(String path:pathList) {
				//压缩包中的文件元素
				path = BaseUtil.swapString(SString.valueOf(path),"\\","//");
				//构建资源信息类
				ResourceEntry re = new ResourceEntry();
				re.codeBase      = zipFilePath;
				re.inJar         = true;
				re.lastModified  = zipFile.lastModified();
				re.source        = path;
				
				//保留开头的/ 所有资源第一个字符都是/
				re.sourceSubPath = path.substring(path.indexOf("!/")+1);
				//资源主键
				String key = re.sourceSubPath;
				if(key!=null && key.startsWith("/")) {
					key = key.substring(1);
				}
				synchronized(sourceMap) {
					//放入资源文件中
					List<ResourceEntry> entrys = sourceMap.get(key);
					if(entrys==null) {
						entrys = new ArrayList<>();
						sourceMap.put(key,entrys);
					}
					entrys.add(re);
				}
			}
		}
	}
	//#endregion
	
	//#region addSource(basePath,file) 加入新的资源文件
	/**
	 * 加入新的资源文件
	 * 用于增加一个资源文件
	 * 刘虻
	 * 2010-8-31 下午12:39:56
	 * @param basePath 根路径
	 * @param file 资源文件对象
	 */
	public void addSource(String basePath,File file) {
		if(file.isDirectory() || !file.exists()) {
			//只处理文件
			return;
		}
		String filePath = file.getPath(); //获取文件全路径
		//资源路径
		String path = BaseUtil.swapString(filePath,"\\","/");
		String checkPath = path.toLowerCase();
		if(checkPath.endsWith("jar") || checkPath.endsWith("zip")) {
			addZipSource(path); //增加压缩包中的内容
			return;
		}
		if(urlList.contains(path)) {
			//该文件已存在
			return;
		}
		urlList.add(path);
		//整理路径格式
		basePath = BaseUtil.swapString(basePath,"\\","/");
		if(basePath.endsWith("/")) {
			//去掉末尾的路径符号
			basePath = basePath.substring(0,basePath.length()-1);
		}
		try {
			//构建资源信息类
			ResourceEntry re = new ResourceEntry();
			re.codeBase      = basePath;
			re.inJar         = false;
			re.lastModified  = file.lastModified();
			re.source        = path;
			try {
				re.sourceSubPath = path.substring(basePath.length());
			}catch(Exception e) {
				throw new MsgException(this,"\nBasePath:"+basePath+"\nPath:"+path,e);
			}
			//资源主键
			String key = re.sourceSubPath;
			if(key!=null && key.startsWith("/")) {
				key = key.substring(1);
			}
			synchronized(sourceMap) {
				//放入资源文件中
				List<ResourceEntry> entrys = sourceMap.get(key);
				if(entrys==null) {
					entrys = new ArrayList<>();
				}
				entrys.add(re);
			}
		}catch(Exception e) {
			e.printStackTrace();
			System.out.println("BasePath:"+basePath+"  File:"+file);
		}
	}
	//#endregion
	
	//#region addClassPath(path) 
	/**
	 * 该方法用于增加一组用分号分割的路径或jar(zip)文件路径,该方法不能用于增加
	 * 一个非jar(zip)的资源文件，因为无法确定文件根路径和相对路径
	 * 加入新的路径
	 * @author 刘虻
	 * 2009-8-17上午01:33:05
	 * @param path        新的绝对路径（只能是单个路径，不能多个路径用分隔符分割）
	 * @param extSubPath  扩展路径（相对路径）
	 */
	public void addClassPath(String path) {
		if(path==null || path.length()<1) {
			return;
		}
		//构建路径对象
		File file = new File(path);
		if(file.isDirectory()) {
			//库文件路径序列
			ArrayList<String> filePathList = new ArrayList<String>();
			filePathList.add(path); //将当前路径放入类路径序列中 比如：classes
			try {
				//搜索指定文件
				SFilesUtil.getFileList(filePathList,path,null,null,true,false);
				for(String ele:filePathList) {
					addSource(path,new File(ele));
				}
			}catch(Exception e) {
				e.printStackTrace();
			}
		}else {
			addSource(path,file);
		}
	}
	//#endregion
}









